/**
 * Copyright 2013 Bart Hanssens
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package be.fedict.fast2web.screener;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

/**
 * Take screen shots of selected web pages using selenium and locally installed
 * (native) browsers.
 */
public class Main 
{
    private final static Logger log = Logger.getLogger(Main.class.getName());
    private static Properties props = new Properties();

    /**
     * Get properties
     * 
     * @return properties 
     */
    private static Properties getProperties() {
        return props;
    }

      
    /**
     * Get properties from a file
     * 
     * @param file
     * @return properties in that file
     */
    private static Properties getProperties(String file) {
        Properties p = new Properties();
        try {
            p.load(new FileReader(file));
        } catch (FileNotFoundException ex) {
            log.error(String.format("Could not find %s", file), ex);
        } catch (IOException ex) {
            log.error(String.format("Could not read %s", file), ex);
        }
        return p;
    }
    
    /**
     * Retrieve the names of one or more browsers to be used
     * 
     * @return list of browser names
     */
    private static List<String> selectDrivers() {
        List<String> drivers = new ArrayList<String>();
        
        String browsers = getProperties().getProperty(Constants.PROP_BROWSER);
        if (browsers != null && ! browsers.equals("")) {
            String tmp[] = browsers.split(",");
            drivers = Arrays.asList(tmp);
        } else {
            log.fatal("No browsers selected");
        }
        return drivers;
    }
    
    /**
     * Try to create subdirectory in parent directory
     * 
     * @param parent name of parent directory
     * @param sub name of subdirectory
     * @return subdirectory
     * @throws IOException 
     */
    private static File createSubDir(File parent, String sub) throws IOException {
        File subDir = new File(parent, sub);
        boolean success = subDir.mkdirs();
        
        if (! success) {
            throw new IOException(String.format("Directory %s could not be created in %s", sub, parent));
        }
        log.debug(String.format("Directory %s created in %s", sub, parent));
        return subDir;
    }

    /**
     * Get output directory with timestamp subdirectory
     * 
     * @return
     * @throws IOException 
     */
    private static File getOutputDir() throws IOException {        
        String d = getProperties().getProperty(Constants.PROP_OUTDIR);
        if (d == null || d.equals("")) {
            throw new IOException(String.format("No output directory, set %s", Constants.PROP_OUTDIR));
        }
        SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd-HHmmss");
        String stamp = fmt.format(new Date());

        return createSubDir(new File(d), stamp);
    }
    
    /**
     * List of config files, each file groups a series of pages 
     * 
     * @return 
     */
    private static List<File> getPageFiles() throws FileNotFoundException {
        String cfgDir = getProperties().getProperty(Constants.PROP_CFG);
        File dir = new File(cfgDir, Constants.SITES_DIR);
        
        // only list .properties files
        File[] listing = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
               return name.endsWith(Constants.CFG_SUFFIX);
            }
        });
        if (listing == null || listing.length == 0) {
            throw new FileNotFoundException(String.format("No site files found in %s", dir.getAbsolutePath()));
        }
        return Arrays.asList(listing);
    }
    
    /**
     * Write screenshot to a file
     * 
     * @param data image data
     * @param dir output directory
     * @param file output file
     * @throws FileNotFoundException
     * @throws IOException 
     */
    private static void writeScreenshot(byte[] data, File dir, String file) throws FileNotFoundException, IOException {
        log.debug(String.format("Writing screenshot to %s", file));
        FileOutputStream f = new FileOutputStream(new File(dir, file));
        f.write(data);
        f.close();
    }
    
    /**
     * Get delay between screenshots
     * 
     * @return delay in milliseconds
     */
    private static long getDelay() {
        long delay = Constants.WAIT_DEFAULT;
        
        String d = getProperties().getProperty(Constants.PROP_WAIT);
        if (d != null && ! d.equals("")) {
            try {
                delay = Long.parseLong(d);
            } catch (NumberFormatException nfe) {
                log.error(String.format("Could not parse %s", Constants.PROP_WAIT, Constants.WAIT_DEFAULT));
            }
        }
        return delay;
    }
  
    /**
     * Sleep for a while
     * 
     * @param delay delay in milliseconds 
     */
    private static void sleep(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ex) {
            log.warn("Sleep interrupted");
        }
    }

    /**
     * Take screen shot with a specific driver / browser
     * 
     * @param driver browser driver
     * @param name short name of the browser
     */
    private static void takeScreenshotAllPages(WebDriver driver, String name) throws IOException {
        File parentDir = getOutputDir();
        File outDir = createSubDir(parentDir, name);
        List<File> groups = getPageFiles();
        
        long delay = getDelay();
        
        // Process all config files
        for(File group : groups) {
            String subDir = group.getName().replace(Constants.CFG_SUFFIX, "");
            File outSubDir = createSubDir(outDir, subDir);
            
            Properties p = getProperties(group.toString());
            Set<String> pages = p.stringPropertyNames();

            // Process all pages in a config file
            for (String page : pages) {
                log.debug(String.format("Page %s", page));
                String url = p.getProperty(page);
                if (url == null || url.equals("")) {
                    log.error("Empty URL");
                    continue;
                }  
                
                // Be nice to the server and wait
                if (delay > 1000) {
                    sleep(delay);
                }
                
                Date start = new Date();
                driver.get(url);
                Date end = new Date();

                log.info(String.format("STATS: %s - %s ms", url, end.getTime() - start.getTime()));
                
                byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
                writeScreenshot(screenshot, outSubDir, String.format("%s.png", page));
            }
        }
    }
    
    /**
     * Set proxy (if needed)
     * 
     * @param capabilities 
     */
    private static void setProxy(DesiredCapabilities capabilities) {
        String p = getProperties().getProperty(Constants.PROP_PROXY);
        if (p != null && !p.equals("")) {
            log.info(String.format("Setting proxy to %s", p));
            Proxy proxy = new Proxy();
            proxy.setHttpProxy(p).setHttpsProxy(p);
            capabilities.setCapability(CapabilityType.PROXY, proxy);
        }
    }
    
    /**
     * Try to load a specific browser /  web driver class
     * 
     * @param name name of the web driver
     * @return instance
     */
    private static RemoteWebDriver loadDriver(String name, Capabilities cap) {
        String d = Constants.PROP_DRIVER_PREFIX + name;
        String driverClass = getProperties().getProperty(d);
        
        RemoteWebDriver driver = null;
        
        try {
            driver = (RemoteWebDriver) Class.forName(driverClass).getConstructor(Capabilities.class).newInstance(cap);
        } catch (SecurityException ex) {
            log.error(String.format("Security exception for class %s", driverClass), ex);
        } catch (IllegalArgumentException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        } catch (ClassNotFoundException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        } catch (InstantiationException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        } catch (IllegalAccessException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        } catch (InvocationTargetException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        } catch (NoSuchMethodException ex) {
            log.error(String.format("Class %s could not be loaded", driverClass), ex);
        }
        return driver;
    }
            
    /**
     * Take a series of screen shots with one or more drivers / browsers
     */
    private static void takeScreenshotsAllDrivers() {
        List<String> driverNames = selectDrivers();
        
        RemoteWebDriver driver;
        DesiredCapabilities capabilities = new DesiredCapabilities();
        setProxy(capabilities);
        
        for (String name : driverNames) {
            driver = loadDriver(name, capabilities);
            driver.manage().deleteAllCookies();
            
            try {
                takeScreenshotAllPages(driver, name);
            } catch (IOException ex) {
                log.error("Taking screenshots failed", ex);
            }
            driver.quit();
        }
    }
    
    public static void main( String[] args ) {
        String cfg = Constants.CFG_DEFAULT;

        if (args.length > 0) {
            File f = new File(args[0]);
            if (f.exists() && f.isDirectory()) {
                cfg = args[0];
            }
        }
     
        File logProp = new File(cfg, Constants.CFG_LOG4J);
        PropertyConfigurator.configure(logProp.toString());

        File selProp = new File(cfg, Constants.CFG_SELENIUM);
        props = getProperties(selProp.toString());
        props.setProperty(Constants.PROP_CFG, cfg);

        log.info("-- Start --");
        log.info(String.format("Using config dir %s ", cfg));
        
        if (props != null) {
            takeScreenshotsAllDrivers();
        }
        log.info("-- End --");
    }
}
